{
	// 加载模块
	let JsonRpc = (typeof(global) === "undefined") ? window.JsonRpc : global.JsonRpc;

	/**
	 * JsonRpc 应用层代理
	 * <p>在 JsonRpc 连接的基础上做了如下封装：
	 * <li>统一了连接地址，屏蔽了 JsonRpc 内部的承载协议的差异；
	 * <li>多个 JsonRpcAgent 可以复用同一个连接
	 * <li>connect()/rpc()/notify()/subscribe() 函数返回异步结果(Promise)，不再使用回调函数机制，从而简化了应用程序结构
	 * <li>封装了默认的调用目标和 token，从而在 rpc()/notify() 时不必再指定
	 */
	class JsonRpcAgent {
		/**
		 * 构造函数
		 * @param {JsonRpc / String} rpcLinkOrAddress JsonRpc 连接对象或连接地址
		 * <li>类型是 Object 时：表示要直接复用“已经存在的 JsonRpc 对象”
		 * <li>类型是 string 时：表示是一个“JsonRpc 连接地址”，这时会使用这个地址新建一个 JsonRpc 对象
		 * @param {String} target JsonRpc 默认目标
		 * @param {String} token JsonRpc 默认鉴权 Token
		 */
	    constructor(rpcLinkOrAddress, target, token) {
			// JsonRpc 连接
	    	this.rpcLink = (typeof rpcLinkOrAddress === "string") ? new JsonRpc(rpcLinkOrAddress) : rpcLinkOrAddress;
			// 目标
	    	this.rpctarget = target;
			// 鉴权 Token
	    	this.rpcToken = token;
	    }

		/**
		 * 设置 Token
		 * @param {String} token JsonRpc 默认鉴权 Token
		 */
		setToken(token) {
			this.rpcToken = token;
		}

		/**
		 * 设置 RPC 调用默认超时时间
		 * @param {int} timeout 超时时间(ms)
		 */
		setRpcTimeout(timeout) { 
			this.rpcLink.setRpcTimeout(timeout); 
		}
		
		/**
		 * 获取所使用的 JsonRpc 连接
		 * @return {JsonRpc} 所使用的 JsonRpc 连接
		 */
		getLink() {
			return this.rpcLink; 
		}
		
		/**
		 * 关闭连接
		 */
		close() {
			if(this.rpcLink != null) { this.rpcLink.close(); this.rpcLink = null; }
		}

		/**
		 * 是否已连接
		 */ 
	    isConnected() {
	    	return (this.rpcLink == null) ? false : this.rpcLink.isConnected();
	    }
 
		/**
		 * 建立 JsonRpc 连接
		 * @return 异步(Promise)结果
		 */
	    connect() {			
			let link = this.rpcLink;
			if(link.isConnected()) {
				return Promise.resolve({code:0});
			} else {
				return new Promise((resolve, reject) => {
					link.onOpen = function() { resolve({code:0}); }
					link.onError = function(error) { resolve(error); }
					link.connect();
				});
			}
	    }

		/**
		 * 发起 RPC 调用
		 * @return 异步(Promise)结果
		 */ 
	    rpc(method, params, timeout) {
	        return new Promise( async (resolve, reject) => {
				if(!this.isConnected()) {
					let res = await this.connect();
					if(res.code != 0) { resolve(res); return; }
				}

				let callback = function(data) {
	                resolve(data);
	            }
	            this.rpcLink.rpc(this.rpctarget, this.rpcToken, method, params, callback, timeout);
	        });
	    }

		/**
		 * 发送 RPC 通知
		 * @return 异步(Promise)结果
		 */
	    notify(method, params) {
	        return new Promise( async (resolve, reject) => {
				if(!this.isConnected()) {
					let res = await this.connect();
					if(res.code != 0) { resolve(res); return; }
				}
	            this.rpcLink.notify(this.rpctarget, this.rpcToken, method, params);
				resolve();
	        });
		}

		/**
		 * 注册事件通知处理器
		 */
		registerNotifyHandler(name, callback) {
	        this.rpcLink.registerNotifyHandler(name, callback);
		}

		/**
		 * 同时 订阅/取消订阅 多个消息过滤器
		 * @return 异步(Promise)结果
		 */
		subscribeEx(addFilters, delFilters, timeout) {
	        return new Promise( async (resolve, reject) => {
				if(!this.isConnected()) {
					let res = await this.connect();
					if(res.code != 0) { resolve(res); return; }
				}

				let callback = function(data) {
	                resolve(data);
	            }
	            this.rpcLink.subscribeEx(addFilters, delFilters, this.rpcToken, callback, timeout);
	        });
		}
	};

	// 把模块放到全局命名空间中去
	// 为保持兼容性应用程序在加载模块时可以使用如下方式：
	(function() {
		return (typeof global !== "undefined") ? global :
			   (typeof self !== "undefined") ? self :
			   (typeof window !== "undefined") ? window : {};
	})()["JsonRpcAgent"] = JsonRpcAgent;
}
